在登入後進入到 kintnoe 主畫面後,都會導向 https://test.cybozu.com/k/#/portal
這個網址,這個網址顯示的畫面就是入口網站,會出現系統預設的畫面,當中有公告欄、未處理事項、應用程式等等區塊,這些區塊的增加或減少可以從「入口網站的設定」中調整:
下方的勾選方框如果未勾選,該區塊就不會顯示,所以我們可以利用這個特性,把全部的區塊都關掉,再自己開發入口網站的介面。雖然你可能覺得會有點多此一舉,但通過自己設計的話彈性會非常高,而且比較漂亮。這篇文章會做一個簡易版的手機選單,下方可以點選切換畫面:
不同於應用程式的開發,portal 開發要將 JS 及 CSS 放在以下:
kintone系統管理 > 透過JavaScript/CSS自訂
把首頁全部的區塊關掉後,我們要做的第一件事就是找到官方提供找 portal 的 DOM 的方法:
kintone.portal.getContentSpaceElement()
kintone.mobile.portal.getContentSpaceElement()
開發的時候記得要根據裝置抓不同的 DOM,至於畫面的排版就交給 CSS Media Query 就好了。
不過要注意的是關閉首頁區塊並沒有區分電腦和手機版,關掉的話就是兩個一起關。這邊為了方便,以下的範例就只有寫手機版。
這邊我用 React + TS 開發,同時會用到 React Router 和 Redux。
// index.tsx
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'
kintone.events.on('mobile.portal.show', () => {
const rootEl = kintone.mobile.portal.getContentSpaceElement()!
const root = ReactDOM.createRoot(rootEl)
root.render(<RouterProvider router={router} />)
})
如果不用 Router,直接用 useState
紀錄當前要顯示哪個組件也可以。Redux
則是因為很多頁其實都抓相同的資料,沒必要再取一次,例如待處理清單。
因為第一次在 redux 裡面寫非同步,也是研究了一下才知道要怎麼用:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { getUnsettledItems } from '../../api'
import type { UnsettledItem } from '../../type'
type SliceState = {
unsettledList: UnsettledItem[]
fetched: boolean
}
const initialState: SliceState = {
unsettledList: [],
fetched: false,
}
// 定義異步 thunk
export const fetchUnsettledList = createAsyncThunk(
'unsettledList/fetchUnsettledList',
async () => {
const response = await getUnsettledItems({ includeGuestInfo: true })
const { assignedAppList } = response.data.result
return assignedAppList
},
)
export const unsettledListSlice = createSlice({
name: 'unsettledList',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchUnsettledList.fulfilled, (state, action) => {
if (state.fetched) return
state.unsettledList = action.payload
state.fetched = true
})
.addCase(fetchUnsettledList.rejected, (state) => {
console.log(state)
})
},
})
export const selectList = (state: { unsettledList: SliceState }) => state.unsettledList
export default unsettledListSlice.reducer
上方有先定義了 fetched
來判斷是否有拿過資料了,因此在組件中調用的時候會是:
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchUnsettledList, selectList } from '../store/slice/unsettledList'
import type { AppDispatch } from '../store'
const About = () => {
const dispatch: AppDispatch = useDispatch()
const states = useSelector(selectList)
useEffect(() => {
if (states.fetched) return
dispatch(fetchUnsettledList())
}, [])
// 下略...
接著把資料渲染出來就好:
<ul>
{states.unsettledList.map((item) => (
<li key={item.id}>
<a href={`${item.id}/?bview=ASSIGN`}>
<img src={item.icon} alt="" />
<div>
<p>{item.name}</p>
<p>待處理項目:{item.count}</p>
</div>
</a>
</li>
))}
</ul>
以上就是這次的開發,其實跟平常在寫前端是一樣的。